Last updated on 2025-Feb-10 at 12:04 PM.
Data collected by Research Assistants Hailey Burns, Kyle McVea, and Carol Thomas.
# load packages
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.1 ✔ tibble 3.2.1
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ✔ purrr 1.0.4
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(here)
## here() starts at /Users/eadie/EadieTech/gsr-study
library(DT)
library(plotly)
##
## Attaching package: 'plotly'
##
## The following object is masked from 'package:ggplot2':
##
## last_plot
##
## The following object is masked from 'package:stats':
##
## filter
##
## The following object is masked from 'package:graphics':
##
## layout
library(viridis)
## Loading required package: viridisLite
library(svglite)
library(htmltools)
library(signal) # For butterworth filter
##
## Attaching package: 'signal'
##
## The following object is masked from 'package:plotly':
##
## filter
##
## The following object is masked from 'package:dplyr':
##
## filter
##
## The following objects are masked from 'package:stats':
##
## filter, poly
sessionInfo()
## R version 4.4.2 (2024-10-31)
## Platform: aarch64-apple-darwin20
## Running under: macOS Ventura 13.7.1
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.0
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## time zone: America/Halifax
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] signal_1.8-1 htmltools_0.5.8.1 svglite_2.1.3 viridis_0.6.5
## [5] viridisLite_0.4.2 plotly_4.10.4 DT_0.33 here_1.0.1
## [9] lubridate_1.9.4 forcats_1.0.0 stringr_1.5.1 dplyr_1.1.4
## [13] purrr_1.0.4 readr_2.1.5 tidyr_1.3.1 tibble_3.2.1
## [17] ggplot2_3.5.1 tidyverse_2.0.0
##
## loaded via a namespace (and not attached):
## [1] sass_0.4.9 generics_0.1.3 stringi_1.8.4 hms_1.1.3
## [5] digest_0.6.37 magrittr_2.0.3 evaluate_1.0.3 grid_4.4.2
## [9] timechange_0.3.0 fastmap_1.2.0 rprojroot_2.0.4 jsonlite_1.8.9
## [13] gridExtra_2.3 httr_1.4.7 scales_1.3.0 lazyeval_0.2.2
## [17] jquerylib_0.1.4 cli_3.6.3 rlang_1.1.5 munsell_0.5.1
## [21] withr_3.0.2 cachem_1.1.0 yaml_2.3.10 tools_4.4.2
## [25] tzdb_0.4.0 colorspace_2.1-1 vctrs_0.6.5 R6_2.5.1
## [29] lifecycle_1.0.4 htmlwidgets_1.6.4 MASS_7.3-64 pkgconfig_2.0.3
## [33] pillar_1.10.1 bslib_0.9.0 gtable_0.3.6 glue_1.8.0
## [37] data.table_1.16.4 systemfonts_1.2.1 xfun_0.50 tidyselect_1.2.1
## [41] rstudioapi_0.17.1 knitr_1.49 rmarkdown_2.29 compiler_4.4.2
# default chunk options
knitr::opts_chunk$set(
comment = '>', cache = TRUE, collapse = TRUE, cache = FALSE, dev= c("png")
)
# load processed data
#load(here("dBdat.Rda"))
We examined GSR data in healthy controls while participants were doing a visual field test (VFT).
Eligible participants were identified among patients of Dr. Brennan Eadie at the Halifax Vision Centre. If deemed eligible for the study, subjects were recruited consecutively.
Each participant underwent 3 study visits, whereupon they performed a VF test on the eligible eye.
The study adhered to the tenets of the Declaration of Helsinki for research involving human subjects and the protocol was approved by the Nova Scotia Health Research Ethics Board (#1027265). All participants gave their written informed consent before enrollment in the study.
Not part of this analysis are datasets from:
Also not looking at heart rate, BP, questionnaire data right now.
df <- tibble(files = list.files(path = "data", pattern = "csv", full.names = T)) %>%
mutate(dfs = map(files, read_csv, skip = 7, col_names=F, show_col_types=FALSE))
plot_list <- lapply(1:nrow(df), function(i) {
data <- df$dfs[[i]]
# Rename columns for clarity (assuming they are currently unnamed)
colnames(data) <- c("timestamp", "heart_rate", "gsr")
# data$timestamp <- trimws(data$timestamp)
data$timestamp <- gsub("^'|'$", "", data$timestamp) # Match start/end quotes
data$timestamp <- gsub("^-", "", data$timestamp)
data$timestamp_duration <- lubridate::hms(data$timestamp)
# 4. Convert to numeric (seconds):
data$timestamp_duration <- as.numeric(data$timestamp_duration)
# Create the plot
ggplot(data, aes(x = timestamp_duration)) +
# geom_line(aes(y = heart_rate, color = "Heart Rate")) +
geom_line(aes(y = gsr)) +
labs(title = df$files[i],
x = "Time (s)",
y = "GSR (uS)") +
theme_minimal()
})
# walk(plot_list, print)
# Convert ggplot objects to plotly objects:
plot_list_plotly <- lapply(plot_list, ggplotly)
# Create the scrolling container:
scrolling_container <- htmltools::tags$div(
style = "overflow-x: scroll; white-space: nowrap; width: 100%;" # Key styles
)
# Add each plot to the scrolling container:
for (i in 1:length(plot_list_plotly)) {
scrolling_container$children[[i]] <- htmltools::div(
style = "display: inline-block; margin-right: 10px;", # Inline and spacing
plot_list_plotly[[i]]
)
}
# Display the scrolling container:
scrolling_container
Plots with 1 Hz lowpass filter and moving average filter applied.
# Function to apply lowpass filter:
lowpass_filter <- function(data, cutoff_freq, sample_rate) {
# Normalize cutoff frequency (Nyquist frequency is 0.5)
cutoff_normalized <- cutoff_freq / (sample_rate / 2)
# Butterworth filter design (adjust order as needed)
bf <- butter(2, cutoff_normalized, type = "low") # 2nd order filter
# Apply filter to gsr data
filtered_gsr <- filtfilt(bf, data$gsr) # Zero-phase filtering
return(filtered_gsr)
}
# Function to apply moving average filter:
moving_average_filter <- function(data, window_size) {
data_length <- length(data$gsr)
# Check if window_size is odd
if (window_size %% 2 == 0) {
warning("window_size was even. Adjusting to the nearest odd number.")
window_size <- window_size + 1 # Make it odd
}
# Check if window_size is smaller than or equal to data length
if (window_size >= data_length) {
warning("window_size is larger than or equal to data length. Reducing window size.")
window_size <- data_length - (data_length %% 2) # Largest odd number less than data length
}
# Now, window_size is GUARANTEED to be odd and LESS THAN data_length
ma_filter <- rep(1/window_size, window_size)
# Use 'filter' from the 'stats' package with 'sides = 2' (centered moving average):
filtered_data <- stats::filter(data$gsr, ma_filter, sides = 2)
# Handle edge cases by replicating the edge values (same as before):
half_window <- (window_size - 1) / 2
filtered_data[1:half_window] <- filtered_data[half_window + 1] # Replicate left edge
filtered_data[(data_length - half_window + 1):data_length] <- filtered_data[data_length - half_window] # Replicate right edge
return(filtered_data)
}
# Loop through plot_list, apply filter, and create plots:
plot_list_filtered <- lapply(1:nrow(df), function(i) {
data <- df$dfs[[i]]
data <- na.omit(data)
# Rename columns for clarity (assuming they are currently unnamed)
colnames(data) <- c("timestamp", "heart_rate", "gsr")
# data$timestamp <- trimws(data$timestamp)
data$timestamp <- gsub("^'|'$", "", data$timestamp) # Match start/end quotes
data$timestamp <- gsub("^-", "", data$timestamp)
data$timestamp_duration <- lubridate::hms(data$timestamp)
# 4. Convert to numeric (seconds):
data$timestamp_duration <- as.numeric(data$timestamp_duration)
# Filter the GSR data:
sample_rate <- 10 # Example sample rate (adjust to your data)
cutoff_freq <- 1 # Example cutoff frequency (1 Hz)
filtered_gsr_lowpass <- lowpass_filter(data, cutoff_freq, sample_rate)
data$gsr_filtered_lowpass <- filtered_gsr_lowpass
# Filter the GSR data (moving average):
window_size <- 3 # Example window size (adjust as needed; must be odd)
filtered_gsr_ma <- moving_average_filter(data, window_size)
data$gsr_filtered_ma <- filtered_gsr_ma
# Create the plotly plot with all three lines:
p <- plot_ly(data, x = ~timestamp_duration) %>%
add_trace(y = ~gsr, type = "scatter", mode = "lines", name = "Original GSR", color = I("#F8766D")) %>%
add_trace(y = ~gsr_filtered_lowpass, type = "scatter", mode = "lines", name = "Lowpass Filtered GSR", color = I("#619CFF")) %>%
add_trace(y = ~gsr_filtered_ma, type = "scatter", mode = "lines", name = "Moving Average Filtered GSR", color = I("#00BA38")) %>%
layout(title = df$files[i],
xaxis = list(title = "Time (s)"),
yaxis = list(title = "GSR (uS)"))
p # Return the plotly plot
})
# Create the scrolling container (same as before):
scrolling_container <- htmltools::tags$div(
style = "overflow-x: scroll; white-space: nowrap; width: 100%;"
)
# Add each plot to the scrolling container:
for (i in 1:length(plot_list_filtered)) {
scrolling_container$children[[i]] <- htmltools::div(
style = "display: inline-block; margin-right: 10px;",
plot_list_filtered[[i]]
)
}
# Display the scrolling container:
scrolling_container
Report by Vivian Eng
vivian@eadietech.com